<!--
 * @Author: TonyJiangWJ
 * @Date: 2022-10-30 10:08:31
 * @Last Modified by: TonyJiangWJ
 * @Last Modified time: 2023-01-04 14:06:02
 * @Description: github: https://github.com/TonyJiangWJ/AutoScriptBase
-->
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>控件可视化</title>
  <link href="./css/materialdesignicons.min.css" rel="stylesheet">
  <link href="./css/vuetify.min.css" rel="stylesheet">
  <script src="./data.js"></script>
  <script src="./img.js"></script>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
  <style>
    .v-treeview-node__label {
      overflow: unset;
    }

    .v-treeview {
      overflow: scroll;
      height: 877px;
    }

    .float_box_discover {
      background-color: #00000000;
      border-radius: 5px;
      /* width: 150px;			 */
      text-align: center;
      position: absolute;
    }

    .v-sheet.v-list {
      background: #00000000;
    }

    .code {
      background-color: #e4e4e4;
      border-radius: 5px;
      padding: 0 5px;
      margin: 0 5px;
    }

    @media screen and (min-device-width: 500px) {
      .left-tree {
        max-width: 100%;
        flex: 0 0 100%;
      }

      .right-canvas {
        max-width: 100%;
        flex: 0 0 100%;
      }
    }

    @media screen and (min-device-width: 1030px) {
      .left-tree {
        max-width: 60%;
        flex: 0 0 60%;
      }

      .right-canvas {
        max-width: 40%;
        flex: 0 0 40%;
      }
    }
  </style>
</head>

<style>
  td {
    word-wrap: break-word;
    word-break: break-word;
    white-space: normal;
  }

  [v-cloak] {
    display: none;
  }
</style>

<body>
  <div id="app" v-cloak>
    <v-app>
      <v-main>
        <v-container>
          <div v-show="findTargetObj" class="float_box_discover"
            :style="{ 'left': winPos.x +'px', 'top': winPos.y+'px' }">
            <v-list>
              <v-list-item>
                <v-btn elevation="2" @click="showDetail(null)">查看详情</v-btn>
              </v-list-item>
              <v-list-item>
                <v-btn elevation="2" @click="removeItem(null)">排除</v-btn>
              </v-list-item>
            </v-list>
          </div>
          <v-row>
            <v-col>
              <p>运行<span class="code">独立工具/获取当前页面的布局信息.js</span>或通过<span class="code">独立工具/布局分析悬浮窗.js</span>触发</p>
              <p>然后将<span class="code">logs/uiobjects.json</span>复制到电脑上，将内容粘贴在下面的uiobjects.json文本框中开始执行分析。</p>
              <p>如果选择了同时截图，则将图片base64文件<span class="code">logs/img.log</span>复制到电脑上，将内容粘贴在下面的图片base64文本框中开始执行分析。</p>
              <p>可以使用ADB复制 如<span class="code">adb pull sdcard/脚本/AutoScriptBase/logs/uiobjects.json
                  ./logs/</span>复制到电脑端的logs目录</p>
              <p>
                推荐直接将<span class="code">logs/data.js</span>复制到电脑上，如<span class="code">adb pull
                  sdcard/脚本/AutoScriptBase/logs/data.js ./控件可视化/</span>
              </p>
              <p>
                对于图片base64数据，将<span class="code">logs/img.js</span>复制到电脑上，如<span class="code">adb pull
                  sdcard/脚本/AutoScriptBase/logs/img.js ./控件可视化/</span>
                执行完刷新浏览器即可
              </p>
              <p>历史获取的控件信息会保存在<span class="code">logs/hisUiObjects</span>下，根据实际情况使用。注意定时清理不需要的文件</p>
            </v-col>
          </v-row>
          <v-row>
            <v-col>
              <v-textarea v-model="uiobjects" label="uiobjects.json内容" :rows="5" row-height="15">
              </v-textarea>
            </v-col>
          </v-row>
          <v-row>
            <v-col>
              <v-textarea v-model="imgBase64" label="图片base64" :rows="2" row-height="15">
              </v-textarea>
            </v-col>
          </v-row>
          <v-row>
            设备宽高{{deviceWidth}} {{deviceHeight}} 总控件数：{{totalWidgets}} 筛选的：{{selectedItems?selectedItems.length:0}}
          </v-row>
          <v-row justify="start" no-gutters>
            <v-col cols="4">
              <v-checkbox v-model="visibleOnly" label="只显示可见节点"></v-checkbox>
            </v-col>
            <v-col cols="4">
              <v-checkbox v-model="hasContent" label="只显示有内容节点[id,desc,text]"></v-checkbox>
            </v-col>
            <v-col cols="4">
              <v-checkbox v-model="inScreen" label="只显示屏幕内节点"></v-checkbox>
            </v-col>
          </v-row>
          <v-row>
            <v-col>
              <p>过滤函数是在树结构中筛选需要的节点，选择函数是自动选中并绘制筛选出来的节点，推荐写法为AutoJS UiSelector的形式。</p>
              <p>例如填写<span class="code">boundsInside(100, 500, 700, 2000).className('android.widget.TextView').filter(v
                  => v.boundsInfo.right - v.boundsInfo.left <= 200)</span>筛选区域内的宽度小于200的TextView节点</p>
            </v-col>
          </v-row>
          <v-row>
            <v-col cols="6">
              <v-textarea v-model="filterFuncStr" label="过滤函数" :rows="3"
                placeholder="如使用 UiSelector 写法为: boundsInside(500, 2000, 700, 2300)" @blur="handleFuncStrChange">
              </v-textarea>
            </v-col>
            <v-col cols="6">
              <v-textarea v-model="selectorFuncStr" label="选择函数" :rows="3"
                placeholder="如使用匿名函数，写法为：node => /.*签到.*/.test(node.desc)" @blur="handleSelectorFuncStrChange">
              </v-textarea>
            </v-col>
          </v-row>
          <v-row>
            <v-col>
              <v-btn elevation="2" @click="openAll">全部打开</v-btn>
              <v-btn elevation="2" @click="closeAll">全部关闭</v-btn>
              <v-btn elevation="2" @click="selectAll">全部选中</v-btn>
            </v-col>
          </v-row>
          <v-row>
            <v-col class="left-tree">
              <v-treeview ref="treeNode" open-all hoverable selectable dense :items="items" :open.sync="opened"
                selection-type="independent" v-model="selectedItems" @update:active="handleActive">
                <template v-slot:label="{ item }">
                  <span :style="{color: item.root.visible?'black':'gray'}"
                    @click="showDetail(item.root)">{{item.name}}</span>
                </template>
              </v-treeview>
            </v-col>
            <v-col class="right-canvas">
              <v-checkbox v-model="drawImageCanvas" label="显示图片"></v-checkbox>
              <canvas id="myCanvas" width="400" height="889" style="border:1px solid #000000;">
                您的浏览器不支持 HTML5 canvas 标签。
              </canvas>
            </v-col>
          </v-row>
        </v-container>
        <v-dialog v-model="dialog" width="700">
          <v-card style="padding: 2rem;">
            <div v-for="(item, i) in uiObjectItems" :key="i">
              <v-row>
                <v-col cols="4">{{item.label}}:</v-col>
                <v-col cols="8">{{item.value}}</v-col>
              </v-row>
            </div>
            <v-card-actions v-if="stackItems.length > 1">
              <v-spacer>总堆叠数：{{showDetailIdx+1}}/{{stackItems.length}}</v-spacer>
              <v-btn color="primary" text :disabled="showDetailIdx==0" @click="showDetailIdx--">
                上一个
              </v-btn>
              <v-btn color="primary" text @click="showDetailIdx++" :disabled="showDetailIdx >= stackItems.length - 1">
                下一个
              </v-btn>
            </v-card-actions>
          </v-card>
        </v-dialog>
    </v-app>
  </div>

  <script src="./js/vue.js"></script>
  <script src="./js/vuetify.js"></script>
  <script>
    let allKey = []
    let allObject = []
    let canvasWidth = 400
    let deviceWidth = 1080
    let deviceHeight = 2340
    let idChecker = new Set()
    let packageName = null
    let columns = [
      {
        key: 'boundsInfo',
        convert: item => {
          let { left, right, top, bottom } = item.boundsInfo
          return `rect:[${left},${top},${right},${bottom}] region:[${left},${top},${right - left},${bottom - top}]`
        }
      },
      { key: 'desc' },
      { key: 'text' },
      { key: 'id' },
      { key: 'depth' },
      { key: 'indexInParent' },
      { key: 'drawingOrder' },
      { key: 'visibleToUser', isBoolean: true },
      { key: 'clickable', isBoolean: true },
      { key: 'longClickable', isBoolean: true },
      { key: 'className' },
      { key: 'packageName' },
      { key: 'checkable', isBoolean: true },
      { key: 'checked', isBoolean: true },
      { key: 'focusable', isBoolean: true },
      { key: 'focused', isBoolean: true },
      { key: 'accessibilityFocused', isBoolean: true },
      { key: 'selected', isBoolean: true },
      { key: 'enabled', isBoolean: true },
      { key: 'password', isBoolean: true },
      { key: 'scrollable', isBoolean: true },
      { key: 'row' },
      { key: 'rowCount' },
      { key: 'rowSpan' },
      { key: 'column' },
      { key: 'columnCount' },
      { key: 'columnSpan' },
    ]
    function buildWithChild (childArray, filter) {
      let rootNode = childArray[0]
      columns.forEach(item => {
        if (item.isBoolean) {
          rootNode[item.key] = falseIfUndefined(item, rootNode[item.key])
        }
        if (item.key == 'packageName') {
          if (typeof rootNode.packageName == "undefined") {
            rootNode.packageName = packageName
          } else {
            packageName = rootNode.packageName
          }
        }
      })
      if (typeof filter != "undefined" && !filter(rootNode, childArray)) {
        console.log('过滤无效节点')
        return null
      }
      allObject.push(rootNode)
      let name = ''
      if (rootNode.id) {
        name += ' id:' + rootNode.id
      }
      if (rootNode.content) {
        name += ` [${!!rootNode.desc ? 'desc' : 'text'}]:${rootNode.content}`
      }
      if (rootNode.boundsInfo) {
        let bounds = rootNode.boundsInfo
        name += ` bounds: [${bounds.left}, ${bounds.top} - ${bounds.right}, ${bounds.bottom}]`
      }
      let { left, top, right, bottom } = rootNode.boundsInfo
      let root = {
        open: true,
        root: rootNode,
        id: `${rootNode.depth}-${rootNode.indexInParent}_${rootNode.id ? rootNode.id : ''}[${left}_${top}_${right}_${bottom}]`,
        name,
        visible: rootNode.visible,
        children: []
      }
      while (idChecker.has(root.id)) {
        console.error('id 重复 ', root.id)
        root.id += 'x'
      }
      idChecker.add(root.id)
      allKey.push(root.id)
      if (childArray.length > 1) {
        let i = 0
        for (let i = 1; i < childArray.length; i++) {
          let child = buildWithChild(childArray[i], filter)
          if (child) {
            root.children.push(child)
          }
        }
        // console.log(root.children)
        root.children = root.children.sort((a, b) => {
          let idxA = a.root.indexInParent
          let idxB = b.root.indexInParent
          return idxA - idxB
        })
      }
      // this.$refs.treeNode.updateAll()
      return root
    }

    function findOpenedParent (root, selectedCache) {
      let opened = []
      findOpened(root, selectedCache, opened)
      return opened
    }

    function findOpened (root, selectedCache, opened) {
      if (selectedCache.has(root.id)) {
        opened.push(root.id)
        return true
      }
      if (!root.children || root.children.length <= 0) {
        return false
      }
      let hasChild = false
      for (let i = 0; i < root.children.length; i++) {
        let child = root.children[i]
        if (selectedCache.has(child.id)) {
          opened.push(root.id)
          hasChild = true
        }
        if (findOpened(child, selectedCache, opened)) {
          opened.push(root.id)
          hasChild = true
        }
      }
      return hasChild
    }
    function falseIfUndefined(item, value) {
      if (typeof value !== "undefined") {
        return value
      }
      if (item.isBoolean) {
        return false
      }
      return undefined
    }
  </script>

  <script>
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      data () {
        return {
          uiobjects: typeof objects != 'undefined' && !!objects ? JSON.stringify(objects) : '',
          // uiobjects: '',
          opened: [],
          visibleOnly: true,
          hasContent: false,
          inScreen: false,
          dialog: false,
          itemDetail: '',
          stackItems: [],
          showDetailIdx: 0,
          selectedItems: [],
          selectedCache: new Set(),
          deviceHeight: null,
          deviceWidth: null,
          filterFuncStr: '',
          filterFunc: null,
          selectorFuncStr: '',
          selectorFunc: null,
          drawedObjects: [],
          drawDuplicatedObjects: [],
          winPos: { x: 0, y: 0 },
          findTargetObj: false,
          clearId: null,
          itemId: null,
          lastFindId: null,
          items: [],
          buildingTimeoutId: null,
          totalWidgets: 0,
          imgBase64: typeof imageBase64 != 'undefined' ? imageBase64 || '' : '',
          drawImageCanvas: false,
        }
      },
      computed: {
        filter: function () {
          console.log('filter changed')
          return (rootNode, childArray) => {
            if (this.visibleOnly) {
              if (!rootNode.visible && !hasVisiableChild(childArray)) {
                return false
              }
            }
            if (this.hasContent) {
              if (!rootNode.content && !rootNode.id && !hasValidChild(childArray, this.filter)) {
                return false
              }
            }
            if (this.inScreen) {
              let { left, top, right, bottom } = rootNode.boundsInfo
              console.log('info:', JSON.stringify({ left, top, right, bottom }))
              let inScreen = widthInScreen(left) && heightInScreen(top) && widthInScreen(right) && heightInScreen(bottom)
              if (!inScreen && !hasValidChild(childArray, this.filter)) {
                return false
              }
            }
            if (this.filterFunc) {
              if (!this.filterFunc(rootNode) && !hasValidChild(childArray, this.filter)) {
                return false
              }
            }
            // return rootNode.className=='com.lynx.tasm.ui.image.FlattenUIImage' || childArray.length > 1
            return true
          }
        },
        uiObjectItems: function () {
          let detail = this.itemDetail ? JSON.parse(this.itemDetail) : null
          if (this.stackItems.length > 1) {
            detail = this.stackItems[this.showDetailIdx].root
          }
          if (detail) {
            let itemInfo = detail
            let labelItems = []
            return columns.map(item => ({ label: item.label || item.key, value: item.convert ? item.convert(itemInfo) : itemInfo[item.key] }))
          }
          return []
        },
      },
      watch: {
        selectedItems: {
          deep: true,
          immediate: true,
          handler: function () {
            console.log('selectedItems changed:', this.selectedItems.length)//, this.selectedItems)
            if (this.items && this.items.length > 0 && this.selectedItems.length > 0) {
              let _this = this
              this.drawCanvas(_this.items, _this.selectedItems)
            } else {
              console.log('无可绘制节点', this.items)
              this.drawedObjects = []
              this.clearCanvas()
            }
          }
        },
        uiobjects: {
          handler () {
            this.buildItems()
          }
        },
        filter: {
          handler () {
            this.buildItems()
          },
          immediate: true,
          deep: true
        },
        visibleOnly: function () {
          this.buildItems()
        },
        hasContent: function () {
          this.buildItems()
        },
        inScreen: function () {
          this.buildItems()
        },
        imgBase64: function () {
          this.$el.drawImg.src = this.imgBase64
        }
      },
      methods: {
        buildItems: function (callback) {
          // debugger
          console.log('触发buildItems')
          // if (this.buildingTimeoutId) {
          //   clearTimeout(this.buildingTimeoutId)
          // }
          let _this = this
          this.buildingTimeoutId = setTimeout(function () {
            if (_this.uiobjects) {
              allKey = []
              allObject = []
              let data = JSON.parse(_this.uiobjects)
              if (data && data.length > 0) {
                _this.deviceHeight = data[0].boundsInfo.bottom
                _this.deviceWidth = data[0].boundsInfo.right
                deviceHeight = _this.deviceHeight
                deviceWidth = _this.deviceWidth
                packageName = data[0].packageName
                // console.log(data)
                // setTimeout(() => {
                idChecker = new Set()
                let root = buildWithChild(data, _this.filter)
                if (_this.selectorFunc) {
                  // setTimeout(function () {
                  _this.selectedCache.clear()
                  _this.autoSelect(root, _this.selectorFunc)
                  let list = []
                  _this.selectedCache.forEach(id => {
                    list.push(id)
                  })
                  // debugger
                  // 延迟变更
                  setTimeout(function () {
                    console.log('build items select items:', list.length)
                    _this.selectedItems = list
                    _this.opened = findOpenedParent(root, _this.selectedCache)
                    callback && callback()
                  }, 20)
                  // }, 100)
                } else {
                  callback && callback()
                }
                _this.items = [root]
                _this.totalWidgets = allKey.length
                // }, 10)
              }
            } else {
              _this.items = []
            }
          }, 10)

        },
        autoSelect: function (obj, selector) {
          if (!obj) {
            return false
          }
          if (selector(obj.root) && !this.selectedCache.has(obj.id)) {
            // console.log('找到匹配的对象 id:', obj.id, 'root:', JSON.stringify(obj.root))
            this.selectedCache.add(obj.id)
          }
          if (obj.children && obj.children.length > 0) {
            obj.children.forEach(child => this.autoSelect(child, selector))
          }
        },
        handleFuncStrChange: function () {
          try {
            console.log('func changed', this.filterFuncStr)
            if (!this.filterFuncStr) {
              this.filterFunc = v => true
              this.buildItems(this.openAll)
              return
            }

            let lambdaCheck = /^\w+\s*=>/
            if (lambdaCheck.test(this.filterFuncStr)) {
              let func = eval(this.filterFuncStr)
              this.filterFunc = func
            } else {
              this.filterFunc = eval('v=>new Selector(v).' + this.filterFuncStr + '.find()')
              console.log('非lambda函数，自动创建selector', this.filterFunc)
            }
            this.buildItems()
          } catch (e) {
            this.filterFunc = null
            console.error(e)
          }
        },
        handleSelectorFuncStrChange: function () {
          try {
            console.log('func changed', this.selectorFuncStr)
            if (!this.selectorFuncStr) {
              this.selectorFunc = null
              this.buildItems(this.openAll)
              return
            }
            let lambdaCheck = /^\w+\s*=>/
            if (lambdaCheck.test(this.selectorFuncStr)) {
              let func = eval(this.selectorFuncStr)
              this.selectorFunc = func
            } else {
              this.selectorFunc = eval('v=>new Selector(v).' + this.selectorFuncStr + '.find()')
              console.log('非lambda函数，自动创建selector', this.selectorFunc)
            }
            this.buildItems()
          } catch (e) {
            this.selectorFunc = null
            console.error(e)
          }
        },
        openAll: function () {
          this.opened = allKey
          // console.log('全部打开', this.$refs.treeNode.open, this.opened)
          // this.$refs.treeNode.updateAll()
        },
        closeAll: function () {
          this.opened = []
        },
        showDetail: function (root) {
          this.dialog = true
          if (root) {
            this.itemDetail = JSON.stringify(root, true)
          }
        },
        selectAll: function () {
          this.selectedItems = allKey
        },
        handleActive: function (items) {
          console.log({
            key: 'active'
          }, items)
        },
        clearCanvas: async function () {
          if (this.$el && this.$el.canvas) {
            this.$el.canvas.width = this.$el.canvas.width
            this.$el.canvas.height = canvasWidth / deviceWidth * deviceHeight
            let scale = canvasWidth / deviceWidth
            let _this = this
            if (_this.$el.drawImg && _this.$el.drawImg.src && _this.drawImageCanvas) {
              _this.$el.ctx.drawImage(_this.$el.drawImg, 0, 0, _this.$el.canvas.width, _this.$el.canvas.height)
            }
            this.$el.ctx.scale(scale, scale)
          }
        },
        drawCanvas: async function (uiObjects, selectedItems) {
          if (!(uiObjects && uiObjects.length > 0)) {
            return
          }
          let tmpSelectCache = new Set()
          this.drawedObjects = []
          this.drawDuplicatedObjects = []
          selectedItems.forEach(key => tmpSelectCache.add(key))
          let drawed = new Set()
          console.log('清空canvas')
          await this.clearCanvas()
          this.$el.ctx.strokeStyle = "#23ff00";
          console.log('绘制')
          let start = new Date().getTime()
          await uiObjects.forEach(object => this.drawChild(object, tmpSelectCache, this.$el.ctx, drawed))
          console.log('绘制完毕，cost', new Date().getTime() - start, 'ms')
        },
        drawChild: function (object, selectedCache, ctx, drawed) {
          let { left, top, right, bottom } = object.root.boundsInfo
          let key = `${left}_${top}_${right}_${bottom}`
          if (selectedCache.has(object.id) && !drawed.has(key) && widthInScreen(left) && widthInScreen(right) && heightInScreen(top) && heightInScreen(bottom)) {
            // console.log('绘制：', left, top, right - left, bottom - top)
            ctx.strokeRect(left, top, right - left, bottom - top)
            drawed.add(key)
            this.drawedObjects.push(object)
          } else if (drawed.has(key)) {
            this.drawDuplicatedObjects.push(object)
          }
          if (object.children && object.children.length > 0) {
            object.children.forEach(child => this.drawChild(child, selectedCache, ctx, drawed))
          }
        },
        findTarget: async function (x, y, showMenu) {
          let realX = x * deviceWidth / canvasWidth
          let realY = y * deviceWidth / canvasWidth
          console.log('实际坐标：', `${realX}, ${realY}`)
          let findObjects = this.drawedObjects.filter(({ root: r }) => {
            let { left, top, right, bottom } = r.boundsInfo
            r.distance = realX - left + right - realX + bottom - realY + realY - top
            return left <= realX && right >= realX && top <= realY && bottom >= realY
          }).sort((a, b) => a.root.distance - b.root.distance)
          if (findObjects.length > 0) {
            this.clearCanvas()
            this.$el.ctx.strokeStyle = '#23ff00'
            this.drawedObjects.forEach(obj => {
              let { left, top, right, bottom } = obj.root.boundsInfo
              this.$el.ctx.strokeRect(left, top, right - left, bottom - top)
            })
            // console.log('找到了匹配的对象:', JSON.stringify(findObjects))
            let _this = this
            this.itemDetail = JSON.stringify(findObjects[0].root)
            this.itemId = findObjects[0].id
            if (showMenu) {
              this.lastFindId = this.itemId
              this.showDetailIdx = 0
              this.stackItems = [findObjects[0]].concat(this.getStackObjects())
            }
            this.$el.ctx.strokeStyle = '#ff0000'
            let { left, top, right, bottom } = findObjects[0].root.boundsInfo
            // console.log('绘制rect', left, top, right - left, bottom - top)
            this.$el.ctx.strokeRect(left, top, right - left, bottom - top)
          }
          this.findTargetObj = this.itemId && this.itemId == this.lastFindId
          if (!this.findTargetObj) {
            this.lastFindId = null
          }
        },
        removeItem: function () {
          let idx = this.selectedItems.indexOf(this.itemId)
          this.selectedItems.splice(idx, 1)
          let stackObjects = this.getStackObjects()
          console.log('堆叠数：', stackObjects.length + 1)
          if (stackObjects.length > 0) {
            stackObjects.forEach(remove => {
              let idx = this.selectedItems.indexOf(remove.id)
              if (idx > -1) {
                console.log('remove', remove.id, 'idx', idx)
                this.selectedItems.splice(idx, 1)
              }
            })
          }
          this.itemId == null
          this.lastFindId = null
          this.findTargetObj = false
        },
        getStackObjects: function () {
          let detailObj = JSON.parse(this.itemDetail)
          return this.drawDuplicatedObjects.filter(v => {
            let key1 = buildKey(v.root)
            let key2 = buildKey(detailObj)
            if (key1 == key2) {
              console.log('remove key: ', key1)
              return true
            }
            return false
            // 
            function buildKey (root) {
              let { left, top, right, bottom } = root.boundsInfo
              return `${left}_${top}_${right}_${bottom}`
            }
          })
        }
      },
      mounted () {
        this.$el.canvas = document.getElementById('myCanvas')
        this.$el.ctx = this.$el.canvas.getContext('2d')
        let _this = this
        this.$el.canvas.onclick = function (event) {
          let x = event.offsetX;
          let y = event.offsetY;
          console.log('点击位置：', `${x}, ${y}`, event)
          _this.winPos = {
            x: event.pageX,
            y: event.pageY,
          }
          _this.findTarget(x, y, true)
        }

        this.$el.canvas.onmousemove = function (e) {
          let x = event.offsetX;
          let y = event.offsetY;
          _this.clearId && clearTimeout(_this.clearId)
          _this.clearId = setTimeout(() => {
            _this.findTarget(x, y)
          }, 5)
        }
        setTimeout(() => {
          _this.buildItems(_this.openAll)
        }, 10)
        function recalculate () {
          canvasWidth = Math.min(400, window.innerWidth - 20)
          console.log('计算canvas width:', canvasWidth)
          _this.$el.canvas.width = canvasWidth
          _this.$el.canvas.height = canvasWidth / deviceWidth * deviceHeight
          if (_this.$el.drawImg && _this.$el.drawImg.src && _this.drawImageCanvas) {
            _this.$el.ctx.drawImage(_this.$el.drawImg, 0, 0, _this.$el.canvas.width, _this.$el.canvas.height)
          }
        }
        recalculate()
        let calculating = null
        window.onresize = function () {
          if (calculating) {
            clearTimeout(calculating)
          }
          calculating = setTimeout(() => recalculate(), 50)
        }
        this.$el.drawImg = new Image()
        window.onload = function () {
          console.log('绑定img load')
          _this.$el.drawImg.onload = function () {
            console.log('image load:', _this.$el.drawImg.width, ',', _this.$el.drawImg.height)
            if (_this.drawImageCanvas) {
              _this.$el.ctx.drawImage(_this.$el.drawImg, 0, 0, _this.$el.canvas.width, _this.$el.canvas.height)
            }
          }
          setTimeout(() => {
            console.log('对img赋值', _this.imgBase64)
            _this.$el.drawImg.src = _this.imgBase64
          }, 100)
        }
      }
    })
  </script>

  <script>
    function hasValidChild (childArray, func) {
      if (!childArray || childArray.length < 1) {
        return false
      }
      if (func(childArray[0])) {
        return true
      }
      for (let i = 1; i < childArray.length; i++) {
        if (hasValidChild(childArray[i], func)) {
          return true
        }
      }
      return false
    }

    function hasVisiableChild(childArray) {
      if (!childArray || childArray.length < 1) {
        return false
      }
      if (childArray[0].visible) {
        return true
      }
      for (let i = 1; i < childArray.length; i++) {
        if (hasVisiableChild(childArray[i])) {
          return true
        }
      }
      return false
    }

    function widthInScreen (x) {
      return x >= 0 && x <= deviceWidth
    }

    function heightInScreen (y) {
      return y >= 0 && y <= deviceHeight
    }
  </script>
  <script>
    function boundsInside (root, l, t, r, b) {
      let { left, top, right, bottom } = root.boundsInfo
      return left >= l && right <= r && top >= t && bottom <= b
    }
    function boundsContains (root, l, t, r, b) {
      let { left, top, right, bottom } = root.boundsInfo
      return left <= l && right >= r && top <= t && bottom >= b
    }
  </script>
  <script>
    function Selector (node) {
      this.filters = []
      this.bounds = function (l, t, r, b) {
        this.filters.push(v => {
          let { left, top, right, bottom } = root.boundsInfo
          return left == l && top == t && right == r && bottom == b
        })
        return this
      }
      this.boundsInside = function (l, t, r, b) {
        this.filters.push(v => boundsInside(v, l, t, r, b))
        return this
      }
      this.boundsContains = function (l, t, r, b) {
        this.filters.push(v => boundsContains(v, l, t, r, b))
        return this
      }

      this.depth = function (depth) {
        this.filters.push(v => v.depth == depth)
        return this
      }

      this.text = function (text) {
        this.filters.push(v => v.text == text)
        return this
      }

      this.textContains = function (text) {
        this.filters.push(v => v.text && v.text.indexOf(text) > -1)
        return this
      }

      this.textMatches = function (text) {
        this.filters.push(v => v.text && new RegExp('^' + text + '$').test(v.text))
        return this
      }

      this.textStartsWith = function (start) {
        this.filters.push(v => v.text && v.text.startsWith(start))
        return this
      }

      this.textEndsWith = function (end) {
        this.filters.push(v => v.text && v.text.endsWith(end))
        return this
      }

      this.desc = function (desc) {
        this.filters.push(v => v.desc == desc)
        return this
      }

      this.descContains = function (desc) {
        this.filters.push(v => v.desc && v.desc.indexOf(desc) > -1)
        return this
      }

      this.descMatches = function (desc) {
        this.filters.push(v => v.desc && new RegExp('^' + desc + '$').test(v.desc))
        return this
      }

      this.descStartsWith = function (start) {
        this.filters.push(v => v.desc && v.desc.startsWith(start))
        return this
      }

      this.descEndsWith = function (end) {
        this.filters.push(v => v.desc && v.desc.endsWith(end))
        return this
      }

      this.id = function (id) {
        this.filters.push(v => v.id == id)
        return this
      }

      this.idContains = function (id) {
        this.filters.push(v => v.id && v.id.indexOf(id) > -1)
        return this
      }

      this.idMatches = function (id) {
        this.filters.push(v => v.id && new RegExp('^' + id + '$').test(v.id))
        return this
      }

      this.idStartsWith = function (start) {
        this.filters.push(v => v.id && v.id.startsWith(start))
        return this
      }

      this.idEndsWith = function (end) {
        this.filters.push(v => v.id && v.id.endsWith(end))
        return this
      }


      this.className = function (className) {
        this.filters.push(v => v.className == className)
        return this
      }

      this.classNameContains = function (className) {
        this.filters.push(v => v.className && v.className.indexOf(className) > -1)
        return this
      }

      this.classNameMatches = function (className) {
        this.filters.push(v => v.className && new RegExp('^' + className + '$').test(v.className))
        return this
      }

      this.classNameStartsWith = function (start) {
        this.filters.push(v => v.className && v.className.startsWith(start))
        return this
      }

      this.classNameEndsWith = function (end) {
        this.filters.push(v => v.className && v.className.endsWith(end))
        return this
      }
      this.drawingOrder = function (check) {
        this.filters.push(v => v.drawingOrder == check)
        return this
      }

      this.clickable = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => {
          let result = v.clickable == check
          if (result) {
            // console.log('check?', check)
            // console.log('clickable:', v.clickable)
            // console.log("v：", JSON.stringify(v))
            return true
          }
          return false
        })
        return this
      }

      this.longClickable = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => v.longClickable == check)
        return this
      }

      this.checkable = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => v.checkable == check)
        return this
      }

      this.selected = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => v.selected == check)
        return this
      }

      this.enabled = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => v.enabled == check)
        return this
      }

      this.scrollable = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => v.scrollable == check)
        return this
      }

      this.editable = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => v.editable == check)
        return this
      }

      this.visibleToUser = function (check) {
        if (typeof check === "undefined") {
          check = true
        }
        this.filters.push(v => v.visibleToUser == check)
        return this
      }

      this.filter = function (func) {
        this.filters.push(func)
        return this
      }

      this.find = function () {
        if (this.filters.length > 0) {
          for (let i = 0; i < this.filters.length; i++) {
            let f = this.filters[i]
            if (!f(node)) {
              return false
            }
          }
        }
        return true
      }
    }
  </script>


</body>

</html>